package initialize

import (
	"errors"
	"fmt"
	"net"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"runtime"
	"sort"
	"strings"

	"github.com/shirou/gopsutil/v3/cpu"
	"github.com/shirou/gopsutil/v3/host"
	"github.com/shirou/gopsutil/v3/mem"
	"gopkg.openfuyao.cn/bkecommon"
	"gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	configinit "gopkg.openfuyao.cn/bkecommon/cluster/initialize"
	configsource "gopkg.openfuyao.cn/bkecommon/source"

	"gopkg.openfuyao.cn/bkeadm/pkg/cluster"
	"gopkg.openfuyao.cn/bkeadm/pkg/common/types"
	"gopkg.openfuyao.cn/bkeadm/pkg/config"
	econd "gopkg.openfuyao.cn/bkeadm/pkg/executor/containerd"
	"gopkg.openfuyao.cn/bkeadm/pkg/global"
	"gopkg.openfuyao.cn/bkeadm/pkg/infrastructure"
	"gopkg.openfuyao.cn/bkeadm/pkg/infrastructure/containerd"
	"gopkg.openfuyao.cn/bkeadm/pkg/infrastructure/kubelet"
	"gopkg.openfuyao.cn/bkeadm/pkg/initialize/bkeagent"
	"gopkg.openfuyao.cn/bkeadm/pkg/initialize/bkeconfig"
	"gopkg.openfuyao.cn/bkeadm/pkg/initialize/bkeconsole"
	"gopkg.openfuyao.cn/bkeadm/pkg/initialize/clusterapi"
	"gopkg.openfuyao.cn/bkeadm/pkg/initialize/repository"
	"gopkg.openfuyao.cn/bkeadm/pkg/initialize/syscompat"
	"gopkg.openfuyao.cn/bkeadm/pkg/initialize/timezone"
	"gopkg.openfuyao.cn/bkeadm/pkg/root"
	"gopkg.openfuyao.cn/bkeadm/utils"
	"gopkg.openfuyao.cn/bkeadm/utils/log"
)

type Options struct {
	root.Options
	File           string   `json:"file"`
	Args           []string `json:"args"`
	HostIP         string   `json:"hostIP"`
	Domain         string   `json:"domain"`
	KubernetesPort string   `json:"kubernetesPort"`
	ImageRepoPort  string   `json:"imageRepoPort"`
	YumRepoPort    string   `json:"yumRepoPort"`
	ChartRepoPort  string   `json:"chartRepoPort"`
	ClusterAPI     string   `json:"clusterAPI"`
	NtpServer      string   `json:"ntpServer"`
	Runtime        string   `json:"runtime"`
	RuntimeStorage string   `json:"runtimeStorage"`
	OtherRepo      string   `json:"otherRepo"`
	OtherSource    string   `json:"otherSource"`
	InstallConsole bool     `json:"installConsole"`
	EnableNTP      bool     `json:"enableNTP"` // 是否启用NTP服务，默认为true

	ImageRepoCAFile    string `json:"imageRepoCAFile"`    // 镜像仓库CA证书文件
	ImageRepoUsername  string `json:"imageRepoUsername"`  // 镜像仓库用户名
	ImageRepoPassword  string `json:"imageRepoPassword"`  // 镜像仓库密码
	ImageRepoTLSVerify bool   `json:"imageRepoTLSVerify"` // 是否验证TLS证书
}

var oc repository.OtherRepo

func (op *Options) Initialize() {
	log.BKEFormat(log.INFO, fmt.Sprint("BKE initialize ..."))
	op.nodeInfo()
	err := op.Validate()
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Validation failure, %s", err.Error()))
		return
	}
	err = op.setTimezone()
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Timezone failure, %s", err.Error()))
		return
	}
	err = op.prepareEnvironment()
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to prepare environment, %s", err.Error()))
		return
	}
	err = op.ensureContainerServer()
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to start the container service, %s", err.Error()))
		return
	}
	err = op.ensureRepository()
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to start warehouse, %s", err.Error()))
		return
	}
	err = op.ensureClusterAPI()
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to start cluster API, %s", err.Error()))
		return
	}
	// 条件性安装 bkeconsole
	if op.InstallConsole {
		err = op.ensureConsoleAll()
		if err != nil {
			log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to start Console, %s", err.Error()))
			return
		}
	} else {
		log.BKEFormat(log.INFO, "Skipping bkeconsole installation as requested")
	}

	// Generating a Configuration File
	op.generateClusterConfig()
	// chmod /bke dir and file permission (reason: openEuler 20.03 umask 0077 need modify permission)
	op.modifyPermission()
	log.BKEFormat(log.INFO, "BKE initialization is complete")
	op.deployCluster()
}

func (op *Options) nodeInfo() {
	h, _ := host.Info()
	c, _ := cpu.Counts(false)
	v, _ := mem.VirtualMemory()

	log.BKEFormat(log.INFO, fmt.Sprintf("HOSTNAME: %s", h.Hostname))
	log.BKEFormat(log.INFO, fmt.Sprintf("PLATFORM: %s", h.Platform))
	log.BKEFormat(log.INFO, fmt.Sprintf("Version:  %s", h.PlatformVersion))
	log.BKEFormat(log.INFO, fmt.Sprintf("KERNEL:   %s", h.KernelVersion))
	log.BKEFormat(log.INFO, fmt.Sprintf("GOOS:     %s", runtime.GOOS))
	log.BKEFormat(log.INFO, fmt.Sprintf("ARCH:     %s", runtime.GOARCH))
	log.BKEFormat(log.INFO, fmt.Sprintf("CPU:      %d", c))
	log.BKEFormat(log.INFO, fmt.Sprintf("MEMORY:   %dG", v.Total/1024/1024/1024+1))

	if op.InstallConsole {
		log.BKEFormat(log.INFO, "BKE Console: ENABLED")
	} else {
		log.BKEFormat(log.INFO, "BKE Console: DISABLED")
	}

}

func (op *Options) Validate() error {
	log.BKEFormat(log.INFO, fmt.Sprint("BKE initialize environment check..."))
	// bke 包是否完整
	var err error

	oc, err = repository.ParseOnlineConfig(op.Domain, op.OtherRepo, op.OtherSource)
	fmt.Sprintf("oc.Repo是%s", oc.Repo)
	fmt.Sprintf("oc.RepoIp是%s", oc.RepoIP)
	fmt.Sprintf("oc.Image是%s", oc.Image)
	if err != nil {
		return errors.New(fmt.Sprintf("Configuration parsing failure %s", err.Error()))
	}

	if op.ImageRepoTLSVerify {
		if op.ImageRepoUsername != "" && op.ImageRepoPassword != "" {
			log.BKEFormat(log.INFO, "Password authentication will be used")
		} else if op.ImageRepoCAFile != "" {
			log.BKEFormat(log.INFO, "CA certificate authentication will be used")
		} else {
			log.BKEFormat(log.WARN, "Client authentication enabled but no credentials provided")
		}
	}

	// host check
	_, free := utils.DiskUsage(global.Workspace)
	if utils.Exists(path.Join(global.Workspace, utils.ImageDataDirectory)) {
		if free/1024/1024/1024 < 3 {
			return errors.New(fmt.Sprintf("The available space of the working directory %s is less than 3 GB",
				global.Workspace))
		}
	} else {
		if free/1024/1024/1024 < utils.MinDiskSpace {
			return errors.New(fmt.Sprintf("The available space of the working directory %s is less than %s GB",
				global.Workspace, utils.MinDiskSpace))
		}
	}

	// port check
	ports := []string{op.KubernetesPort, op.ImageRepoPort, op.ChartRepoPort, op.YumRepoPort, "2049"}
	if infrastructure.IsDocker() {
		if _, ok := global.Docker.ContainerExists(utils.LocalKubernetesName); ok {
			ports = utils.RemoveStringObject(ports, op.KubernetesPort)
		}
		if _, ok := global.Docker.ContainerExists(utils.LocalImageRegistryName); ok {
			ports = utils.RemoveStringObject(ports, op.ImageRepoPort)
		}
		if _, ok := global.Docker.ContainerExists(utils.LocalChartRegistryName); ok {
			ports = utils.RemoveStringObject(ports, op.ChartRepoPort)
		}
		if _, ok := global.Docker.ContainerExists(utils.LocalNFSRegistryName); ok {
			ports = utils.RemoveStringObject(ports, "2049")
		}
		if _, ok := global.Docker.ContainerExists(utils.LocalYumRegistryName); ok {
			ports = utils.RemoveStringObject(ports, op.YumRepoPort)
		}
	}
	if infrastructure.IsContainerd() {
		if _, ok := econd.ContainerExists(utils.LocalKubernetesName); ok {
			ports = utils.RemoveStringObject(ports, op.KubernetesPort)
		}
		if _, ok := econd.ContainerExists(utils.LocalImageRegistryName); ok {
			ports = utils.RemoveStringObject(ports, op.ImageRepoPort)
		}
		if _, ok := econd.ContainerExists(utils.LocalChartRegistryName); ok {
			ports = utils.RemoveStringObject(ports, op.ChartRepoPort)
		}
		if _, ok := econd.ContainerExists(utils.LocalNFSRegistryName); ok {
			ports = utils.RemoveStringObject(ports, "2049")
		}
		if _, ok := econd.ContainerExists(utils.LocalYumRegistryName); ok {
			ports = utils.RemoveStringObject(ports, op.YumRepoPort)
		}
	}

	err = utils.CheckPorts(ports)
	if err != nil {
		return errors.New(fmt.Sprintf("The port is already in use %s", err.Error()))
	}

	global.CustomExtra["domain"] = op.Domain
	global.CustomExtra["host"] = op.HostIP
	global.CustomExtra["imageRepoPort"] = op.ImageRepoPort
	global.CustomExtra["yumRepoPort"] = op.YumRepoPort
	global.CustomExtra["chartRepoPort"] = op.ChartRepoPort
	global.CustomExtra["clusterapi"] = op.ClusterAPI
	global.CustomExtra["nfsserverpath"] = "/"
	global.CustomExtra["otherRepo"] = oc.Repo
	global.CustomExtra["otherRepoIp"] = oc.RepoIP
	global.CustomExtra["otherSource"] = oc.Source
	return nil
}

func (op *Options) setTimezone() error {
	log.BKEFormat(log.INFO, "set up the host machine zone")
	err := timezone.SetTimeZone()
	if err != nil {
		return err
	}
	// 如果禁用了NTP服务，则跳过NTP服务器设置
	if !op.EnableNTP {
		log.BKEFormat(log.INFO, "NTP service is disabled, skipping NTP server setup")
		op.NtpServer = "" // 设置为空字符串，后续配置会跳过NTP设置
		return nil
	}
	log.BKEFormat(log.INFO, "set ntp server")
	newNTPServer, err := timezone.NTPServer(op.NtpServer, op.HostIP, len(oc.Repo) > 0)
	if err != nil {
		return err
	}
	op.NtpServer = newNTPServer
	return nil
}

func (op *Options) prepareEnvironment() error {
	log.BKEFormat(log.INFO, "config local source")
	// 注：只有离线按照需要配置本地bke repo
	if op.OtherRepo == "" {
		baseurl := "file://" + path.Join(global.Workspace, utils.SourceDataDirectory)
		if len(oc.Source) > 0 {
			baseurl = oc.Source
		}
		err := configsource.SetSource(baseurl)
		if err != nil {
			log.BKEFormat(log.WARN, fmt.Sprintf("Failed to set download source %s", err.Error()))
		}
	}

	hostIP, domain := op.HostIP, op.Domain

	// 新增：区分在线/离线模式，调整hostIP的绑定逻辑
	if op.OtherRepo != "" {
		// 在线模式：otherRepo非空，尝试从oc.Repo中提取IP（若otherRepo是IP形式）
		registryHost, _ := repository.ParseRegistryHostPort(op.OtherRepo)
		// 判断提取的registryHost是否为IP（而非域名）
		if net.ParseIP(registryHost) != nil {
			// 若otherRepo是IP形式，将domain绑定到该IP
			hostIP = registryHost
			log.BKEFormat(log.INFO, fmt.Sprintf("在线模式：domain：%s 绑定到otherRepo的IP：%s", domain, hostIP))
		} else {
			// 若otherRepo是域名形式，沿用原逻辑（可保留默认或根据需要调整）
			log.BKEFormat(log.INFO, fmt.Sprintf("在线模式：domain：%s 绑定到默认IP：%s", domain, hostIP))
		}
	} else {
		// 离线模式：otherRepo为空，domain绑定到引导节点IP（op.HostIP）
		log.BKEFormat(log.INFO, fmt.Sprintf("离线模式：domain：%s 绑定到引导节点IP：%s", domain, hostIP))
	}

	if strings.Contains(oc.Repo, configinit.DefaultImageRepo) {
		hostIP = oc.RepoIP
		domain = strings.Split(strings.Split(oc.Repo, "/")[0], ":")[0]
	}
	err := syscompat.SetHosts(hostIP, domain)
	if err != nil {
		log.BKEFormat(log.WARN, fmt.Sprintf("Failed to set hosts %s", err.Error()))
	}

	clientAuthConfig := &repository.CertificateConfig{
		TLSVerify: op.ImageRepoTLSVerify,
		Username:  op.ImageRepoUsername,
		Password:  op.ImageRepoPassword,
		CAFile:    op.ImageRepoCAFile,
	}

	// 新增：解析私有仓库地址（从oc.Repo中提取主机和端口）并配置CA证书
	// oc.Repo格式示例：my.harbor.com:443/openfuyao/
	registryHost, registryPort := repository.ParseRegistryHostPort(oc.Repo)
	if registryHost != "" && registryPort != "" {
		clientAuthConfig.RegistryHost = registryHost
		clientAuthConfig.RegistryPort = registryPort
		// 若启用TLS验证，配置CA证书到containerd信任目录
		if clientAuthConfig.TLSVerify && clientAuthConfig.CAFile != "" {
			if err := repository.SetupCACertificate(clientAuthConfig); err != nil {
				return fmt.Errorf("配置私有仓库CA证书失败：%v", err)
			}
		}
	} else {
		log.BKEFormat(log.WARN, "无法解析私有仓库地址，跳过CA证书配置")
	}

	// 优先尝试在线下载(带客户端认证,只在有镜像的场景下起作用)
	err = repository.RepoInit(oc, clientAuthConfig)
	if err != nil {
		return err
	}
	// 解压关键包
	err = repository.DecompressionSystemSourceFile()
	if err != nil {
		return err
	}
	// online environment prepare
	err = repository.SourceInit(oc)
	if err != nil {
		return err
	}
	// system compat
	err = syscompat.RepoUpdate()
	if err != nil {
		log.BKEFormat(log.WARN, fmt.Sprintf("Failed to update repo %s", err.Error()))
	}
	err = syscompat.Compat()
	if err != nil {
		return errors.New(fmt.Sprintf("The system is not compatible %s", err.Error()))
	}
	syscompat.SetSysctl()
	return nil
}

func (op *Options) ensureContainerServer() error {
	err := repository.PrepareRepositoryDependOn()
	if err != nil {
		return err
	}
	op.modifyPermission()
	filePath, containerdList, cniPluginList, err := repository.VerifyContainerdFile()
	if err != nil {
		return err
	}
	containerdFile := containerdList[0]
	cniPluginFile := cniPluginList[0]
	for _, cond := range containerdList {
		if strings.Contains(cond, runtime.GOARCH) {
			containerdFile = cond
			continue
		}
	}
	for _, cni := range cniPluginList {
		if strings.Contains(cni, runtime.GOARCH) {
			cniPluginFile = cni
			continue
		}
	}
	err = infrastructure.RuntimeInstall(op.Runtime, op.RuntimeStorage, fmt.Sprintf("%s:%s", op.Domain, op.ImageRepoPort),
		filePath+"/"+containerdFile, filePath+"/"+cniPluginFile, filePath+"/"+strings.Replace(utils.KylinDocker, "{.arch}", runtime.GOARCH, -1),
		op.HostIP, op.ImageRepoCAFile)
	if err != nil {
		return err
	}
	return nil
}

func (op *Options) ensureRepository() error {
	log.BKEFormat(log.INFO, "Start the base dependency service")
	var err error
	err = repository.LoadLocalRepository()
	if err != nil {
		return err
	}
	err = repository.ContainerServer(op.ImageRepoPort, oc.Repo)
	if err != nil {
		return err
	}
	err = repository.YumServer(op.ImageRepoPort, op.YumRepoPort, oc.Repo)
	if err != nil {
		return err
	}
	err = repository.ChartServer(op.ImageRepoPort, op.ChartRepoPort, oc.Repo)
	if err != nil {
		return err
	}
	err = repository.NFSServer(op.ImageRepoPort, oc.Repo)
	if err != nil {
		return err
	}
	return nil
}

func (op *Options) ensureClusterAPI() error {
	err := infrastructure.StartLocalKubernetes(oc.Repo, oc.RepoIP, op.HostIP, op.Domain, op.ImageRepoPort, op.KubernetesPort)
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to start kubernetes %s", err.Error()))
		return err
	}

	err = containerd.ApplyContainerdCfg(fmt.Sprintf("%s:%s", op.Domain, op.ImageRepoPort))
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to install containerd config %s", err.Error()))
		return err
	}

	err = kubelet.ApplyKubeletCfg()
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to install kubelet config %s", err.Error()))
		return err
	}

	err = bkeagent.InstallBKEAgentCRD()
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to install bkeagent %s", err.Error()))
		return err
	}

	repo := fmt.Sprintf("%s:%s/%s/", op.Domain, "443", bkecommon.ImageRegistryKubernetes)
	if oc.Repo != "" {
		repo = oc.Repo
	}
	err = clusterapi.DeployClusterAPI(repo, op.ClusterAPI)
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to deploy cluster-api %s", err.Error()))
		return err
	}
	log.BKEFormat(log.INFO, "The cluster-api deployment is complete")
	return nil
}

func (op *Options) ensureConsoleAll() error {
	// 检查是否启用 console 安装
	if !op.InstallConsole {
		log.BKEFormat(log.INFO, "BKE Console installation is disabled")
		return nil
	}

	log.BKEFormat(log.INFO, "Starting BKE Console installation...")
	repo := fmt.Sprintf("%s:%s/%s/", op.Domain, "443", bkecommon.ImageRegistryKubernetes)
	if oc.Repo != "" {
		repo = oc.Repo
	}
	var sRestartConfig types.K3sRestartConfig
	sRestartConfig = types.K3sRestartConfig{
		OtherRepo:      oc.Repo,
		OtherRepoIp:    oc.RepoIP,
		HostIP:         op.HostIP,
		ImageRepo:      op.Domain,
		ImageRepoPort:  op.ImageRepoPort,
		KubernetesPort: op.KubernetesPort,
	}

	err := bkeconsole.DeployConsoleAll(sRestartConfig, repo)
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to deloy console %s", err.Error()))
		return err
	}
	log.BKEFormat(log.INFO, "The bke console deployment is complete")
	return nil
}

func (op *Options) generateClusterConfig() {
	log.BKEFormat(log.INFO, "Generate the cluster configuration file")

	data, repo, err := op.prepareClusterConfigData()
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("generateClusterConfig func is error %s", err))
		return
	}

	op.createClusterConfigFile(data, repo[0], repo[1])
}

func (op *Options) prepareClusterConfigData() (map[string]string, []v1beta1.Repo, error) {
	k8sVersion, err := op.getStartKubernetesVersion()
	if err != nil {
		return nil, []v1beta1.Repo{}, err
	}

	data := map[string]string{
		"chartRepoPort":     fmt.Sprintf("%s", op.ChartRepoPort),
		"clusterapi":        op.ClusterAPI,
		"domain":            op.Domain,
		"host":              op.HostIP,
		"httpDomain":        configinit.DefaultYumRepo,
		"httpIp":            "",
		"httpRepo":          oc.Source,
		"imageRepoPort":     fmt.Sprintf("%s", op.ImageRepoPort),
		"imageRepoPrefix":   bkecommon.ImageRegistryKubernetes,
		"ntpServer":         op.NtpServer,
		"otherRepo":         oc.Repo,
		"otherRepoIp":       oc.RepoIP,
		"runtime":           op.Runtime,
		"yumRepoPort":       fmt.Sprintf("%s", op.YumRepoPort),
		"kubernetesVersion": k8sVersion,
	}

	if patchMap := op.ProcessPatchFiles(); patchMap != nil {
		for k, v := range patchMap {
			data[k] = v
		}
	}

	repo := op.prepareImageRepoConfig()

	repo2 := op.prepareHTTPRepoConfig()

	for k, v := range global.CustomExtra {
		data[k] = v
	}

	return data, []v1beta1.Repo{repo, repo2}, nil
}

func (op *Options) prepareImageRepoConfig() v1beta1.Repo {
	repo := v1beta1.Repo{
		Domain: op.Domain,
		Ip:     op.HostIP,
		Port:   op.ImageRepoPort,
		Prefix: bkecommon.ImageRegistryKubernetes,
	}

	if oc.Repo != "" {
		img := strings.Split(oc.Repo, "/")
		img1 := strings.Split(img[0], ":")
		port := "443"
		if len(img1) == utils.HttpUrlFields {
			port = img1[1]
		}
		repo = v1beta1.Repo{
			Domain: img1[0],
			Ip:     oc.RepoIP,
			Port:   port,
			Prefix: strings.TrimRight(strings.Join(img[1:], "/"), "/"),
		}
	}

	return repo
}

func (op *Options) prepareHTTPRepoConfig() v1beta1.Repo {
	repo2 := v1beta1.Repo{
		Domain: configinit.DefaultYumRepo,
		Ip:     op.HostIP,
		Port:   op.YumRepoPort,
	}

	if oc.Source != "" {
		httpRepo := strings.TrimLeft(oc.Source, "http://")
		httpRepoArray := strings.Split(httpRepo, ":")
		port := "80"
		if len(httpRepoArray) == utils.HttpUrlFields {
			port = httpRepoArray[1]
		}

		repo2 = v1beta1.Repo{
			Domain: configinit.DefaultYumRepo,
			Port:   port,
		}

		if net.ParseIP(httpRepoArray[0]) == nil {
			global.CustomExtra["httpIp"] = httpRepoArray[0]
			repo2 = v1beta1.Repo{
				Ip: httpRepoArray[0],
			}
		} else {
			repo2 = v1beta1.Repo{
				Domain: httpRepoArray[0],
			}
		}
	}

	return repo2
}

func (op *Options) createClusterConfigFile(data map[string]string, repo, repo2 v1beta1.Repo) {
	err := bkeconfig.SetKubernetesConfig(data, bkecommon.BKEClusterConfigFileName, "cluster-system")
	if err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("Failed to generate the cluster configuration file %s", err.Error()))
		return
	}

	if op.File != "" {
		return
	}

	// 创建集群配置
	c := config.Options{
		Directory:     fmt.Sprintf("%s/cluster", global.Workspace),
		Product:       "fuyao-allinone",
		Domain:        op.Domain,
		ImageRepoPort: op.ImageRepoPort,
	}
	c.Config(global.CustomExtra, repo, repo2, op.NtpServer)

	log.BKEFormat(log.HINT, fmt.Sprintf("Run `bke cluster create -f %s/cluster/1master.yaml` "+
		"command to deploy the cluster", global.Workspace))
}

func versionLess(v1, v2 string) bool {
	num1 := strings.TrimPrefix(v1, "v")
	num2 := strings.TrimPrefix(v2, "v")

	parts1 := strings.Split(num1, ".")
	parts2 := strings.Split(num2, ".")

	for i := 0; i < len(parts1) && i < len(parts2); i++ {
		if parts1[i] != parts2[i] {
			return parts1[i] < parts2[i]
		}
	}

	return len(parts1) < len(parts2)
}

func (op *Options) getStartKubernetesVersion() (string, error) {
	sourceRegistry := fmt.Sprintf("%s/mount/source_registry/files", global.Workspace)
	entries, err := os.ReadDir(sourceRegistry)
	if err != nil {
		return "", err
	}
	// 修改正则：允许版本后带 "-amd64" 等后缀（新增 (-[a-zA-Z0-9]+)? 部分）
	re := regexp.MustCompile(`kubectl-(v\d+\.\d+\.\d+)(-[a-zA-Z0-9]+)?`)

	var versions []string

	for _, entry := range entries {
		matches := re.FindStringSubmatch(entry.Name())
		if len(matches) > 1 {
			versions = append(versions, matches[1])
		}
	}

	if len(versions) == 0 {
		return "", errors.New("no kubernetes version found")
	}

	sort.Slice(versions, func(i, j int) bool {
		return versionLess(versions[i], versions[j])
	})

	return versions[0], nil
}

func (op *Options) ProcessPatchFiles() map[string]string {
	patchesDir := filepath.Join(global.Workspace, utils.PatchDataDirectory)
	if _, err := os.Stat(patchesDir); os.IsNotExist(err) {
		return nil
	}

	entries, err := os.ReadDir(patchesDir)
	if err != nil {
		return nil
	}

	patchFiles := make(map[string]string)

	for _, entry := range entries {
		info, err := entry.Info()
		if err != nil {
			fmt.Printf("Warning: failed to get file info for %s: %v\n", entry.Name(), err)
			continue
		}

		if info.Size() == 0 {
			fmt.Printf("Warning: skipping empty file: %s\n", entry.Name())
			continue
		}

		version := strings.TrimSuffix(entry.Name(), filepath.Ext(entry.Name()))
		fullPath := filepath.Join(patchesDir, entry.Name())
		// 根据文件内容构造数据，将patch内容写入到configmap，为了便于处理，统一使用 openfuyao-patch 命名空间
		bkeConfigMapKey := fmt.Sprintf("%s%s", utils.PatchKeyPrefix, version)
		patchConfigMapName := fmt.Sprintf("%s%s", utils.PatchValuePrefix, version)
		if err = bkeconfig.SetPatchConfig(version, fullPath, patchConfigMapName); err != nil {
			continue
		}
		patchFiles[bkeConfigMapKey] = patchConfigMapName
	}

	return patchFiles
}

func (op *Options) modifyPermission() {
	var workDir string
	if utils.IsFile("/opt/BKE_WORKSPACE") {
		f, err := os.ReadFile("/opt/BKE_WORKSPACE")
		if err == nil {
			workDir = string(f)
			workDir = strings.TrimSpace(workDir)
			workDir = strings.TrimRight(workDir, "\n")
			workDir = strings.TrimRight(workDir, "\r")
			workDir = strings.TrimRight(workDir, "\t")
		}
	}

	if os.Getenv("BKE_WORKSPACE") != "" {
		workDir = os.Getenv("BKE_WORKSPACE")
	}
	if workDir == "" {
		workDir = "/bke"
	}

	// modify permission: file 644 dir 755
	err := filepath.Walk(workDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			log.BKEFormat(log.INFO, fmt.Sprintf("Walk %s err %v", workDir, err))
			return err
		}

		if info.IsDir() {
			err = os.Chmod(path, utils.DefaultDirPermission)
			if err != nil {
				log.BKEFormat(log.INFO, fmt.Sprintf("path %s mod dir permission err %v", path, err))
			}
		} else {
			err = os.Chmod(path, utils.DefaultFilePermission)
			if err != nil {
				log.BKEFormat(log.INFO, fmt.Sprintf("path %s mod file permission err %v", path, err))
			}
		}
		return nil
	})

	if err != nil {
		log.BKEFormat(log.INFO, fmt.Sprintf("workDir %s mod permission err %v", workDir, err))
	} else {
		log.BKEFormat(log.INFO, fmt.Sprintf("workDir %s mod permission success", workDir))
	}
}

func (op *Options) deployCluster() {
	if op.File == "" {
		return
	}
	log.BKEFormat(log.INFO, "Starting to deploy the cluster...")
	c := cluster.Options{
		File:      op.File,
		NtpServer: op.NtpServer,
	}
	c.Cluster()
}
